#!/usr/bin/env python
from django.core.management import setup_environ
try:
    import settings # Assumed to be in the same directory.
except ImportError:
    import sys
    sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
    sys.exit(1)
setup_environ(settings)


from collections import defaultdict
from datetime import datetime, timedelta
from django.db.models import Count
from opinion_extractor.opinion.models import Opinion
from opinion_extractor.questionnaire.models import Questionnaire


def aggregate_via_db(questionnaire, valid_on):
    questions = questionnaire.questions.all()
    all_answers = [q.answers for q in questions]
    num_questions = len(questions)

    query = Opinion._default_manager.filter(questionnaire=questionnaire).valid_on_date(valid_on)
    num_opinions = query.count()

    questions_spec = []
    if num_opinions > 0:
        for i, answer_field in enumerate(Opinion._meta.answer_fields[:num_questions]):
            question = questions[i]
            # Prefill all opinion counts for every anser to 0 and then
            # insert real number of opinions where > 0
            counts = dict.fromkeys(range(question.num_answers), 0)
            counts.update(map(
                lambda x: (x[answer_field], x['answers']),
                query.values(answer_field).annotate(
                    answers=Count(answer_field)
                ).order_by(answer_field)
            ))
            opinions= map(
                lambda (key, count): dict(
                    title=question.answers[key],
                    count=count
                ),
                counts.items()
            )
            questions_spec.append(dict(
                title=question.title,
                opinions=opinions,
            ))
    return questions_spec


def aggregate_via_python(questionnaire, valid_on):
    questions = questionnaire.questions.all()
    all_answers = [q.answers for q in questions]
    num_questions = len(questions)
    questions_range = range(num_questions)
    answer_fields = Opinion._meta.answer_fields[:num_questions]

    raw_opinions = Opinion._default_manager.filter(
        questionnaire=questionnaire
    ).valid_on_date(valid_on).values_list(*answer_fields)

    if len(raw_opinions) == 0:
        return []
    
    counts = [defaultdict(int) for q in questions_range]
    for r in raw_opinions:
        for q in questions_range:
            key = r[q]
            counts[q][key] += 1

    questions_spec = []
    for q in questions_range:
        answers = all_answers[q]
        q_counts = counts[q]
        opinions = map(
            lambda (key, title): dict(
                title=title,
                count=q_counts[key]
            ),
            enumerate(answers)
        )
        questions_spec.append(dict(
            title=questions[q].title,
            opinions=opinions,
        ))
    return questions_spec


def time_it(func, num_iterations, *func_args, **func_kwargs):
    times = []
    last_result = None
    for i in xrange(num_iterations):
        start = datetime.now()
        x = func(*func_args, **func_kwargs)
        times.append(datetime.now() - start)
        if i == 0:
            last_result = x
        else:
            if x != last_result:
                raise RuntimeError("Got different results between calls of %s" % func.__name__)
    avg_time = sum(times, timedelta())/num_iterations
    return (avg_time, last_result)


NUM_ITERATIONS = 20


def compare_times(functions, num_iterations, questionnaire, valid_on):
    last_result = None
    for i, func in enumerate(functions):
        func_name = func.__name__
        print "Testing '%s' function with %d iterations..." % (func_name, num_iterations)
        avg_time, x = time_it(func, num_iterations, questionnaire, valid_on)
        if i == 0:
            last_result = x
        else:
            if x != last_result:
                print last_result
                print x
                raise RuntimeError("Function '%s' returned a different result than previous functions!" % func_name)
        print "Function '%s' average time was %s seconds.\n" % (func_name, avg_time)


if __name__ == '__main__':
    questionnaire = Questionnaire.objects.get(pk=1)
    valid_on = datetime.now()
    
    from django.db import connection
    old_queries_num = len(connection.queries)
    
    functions = [v for k, v in locals().items() if k.startswith('aggregate_via_')]
    compare_times(functions, NUM_ITERATIONS, questionnaire, valid_on)
    
    queries = connection.queries[old_queries_num:]
    unique_queries = defaultdict(list)
    for q in queries:
        key = q['sql']
        unique_queries[key].append(float(q['time']))

    from numpy.core.ma import average
    print "\n\n\nThe following unique queries have been run:\n\n"
    for uq, times in unique_queries.items():
        print "%s\n" % uq
        #print "Times (seconds): %s" % times
        #print "Sorted times (seconds): %s" % sorted(times)
        print "This query has been run %d times" % len(times)
        print "Average time is %f seconds\n\n" % average(times)
